package com.white.mybatis.dynamic;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.white.mybatis.dynamic.exceptions.DynamicMapperException;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

/**
 * <p>
 * 动态Mapper
 * 主要针对于数据库组合表且动态字段的映射与增删改查的能力
 * </p>
 *
 * @author huangzy
 * @since 2022/2/22
 */
public abstract class BaseDynamicMppaer implements IDynamicMapper , ReloadMapper , ApplicationRunner {

    /**
     * sql语句操作
     */
    @Resource
    private JdbcTemplate jdbcTemplate;

    /**
     * 类注解信息
     */
    private final DynamicMppaer dynamicMppaer;

    /**
     * 动态表信息提取器
     */
    private DynamicTableExtractor dynamicTableExtractor;

    /**
     * 日志
     */
    private final DynamicMapperLogger logger;

    /**
     * 初始化Mapper
     */
    public BaseDynamicMppaer() {
        this.logger = new DynamicMapperLogger(this.getClass());
        this.dynamicMppaer = this.getClass().getAnnotation(DynamicMppaer.class);
        if (this.dynamicMppaer == null) {
            throw new DynamicMapperException("需要使用 " + DynamicMppaer.class.getName() + " 注解");
        }

    }

    @Override
    public void run(ApplicationArguments args) throws Exception {
        //初始化动态表信息提取器
        this.dynamicTableExtractor = new DynamicTableExtractor(jdbcTemplate,getDynamicTable().stream()
                .map(DynamicTable::value)
                .toArray(String[]::new));
    }

    /**
     * 获取表集合
     */
    private List<DynamicTable> getDynamicTable(){
        List<DynamicTable> tables = new ArrayList<>(Collections.singletonList(this.dynamicMppaer.master()));
        tables.addAll(Arrays.stream(this.dynamicMppaer.slave()).collect(Collectors.toList()));
        return tables;
    }

    /**
     * 对入参Map进行按表名分组
     *
     * @param entity 入参
     * @return 返回字段与值的包装类Map
     */
    private Map<String, List<TableFieldValue>> parseFieldValue(Map<String, Object> entity) {
        //按照表名分组,Key为表名,Value为entity Key的集合
        return entity.keySet().stream().collect(Collectors.groupingBy(item -> {
            if (!item.contains("_")) {
                return StringUtils.underlineToCamel(dynamicMppaer.master().value());
            }
            String[] tableAndField = item.split("_");
            if (tableAndField.length != 2) {
                throw new DynamicMapperException("字段入参不合法");
            }
            return StringUtils.underlineToCamel(tableAndField[0]);
        })).entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, entry ->
                entry.getValue().stream()
                        .map(item -> {
                            TableField tableField = dynamicTableExtractor.getTableFieldByProperty(item);
                            if (tableField != null) {
                                return new TableFieldValue(tableField, entity.get(item));
                            }
                            return null;
                        }).collect(Collectors.toList())
        ));
    }

    /**
     * 获取主从表之间相互关联的值在传入Wrapper条件匹配后的值集合
     *
     * 获取仅查询注解@DynamicMppaer主表 的onField()属性的数据列表
     * @param wrapper 条件
     */
    private List<Object> getUniqueCondition(QueryWrapper<?> wrapper){
        TableField tableField = dynamicTableExtractor.getTableFieldByColumn(dynamicMppaer.master().value(), dynamicMppaer.master().onField());
        if (tableField == null) {
            throw new DynamicMapperException(String.format("表: %s,不存在该字段: %s",dynamicMppaer.master().value(),dynamicMppaer.master().onField()));
        }
        //获取仅查询注解@DynamicMppaer主表 的onField()属性的数据列表
        String selectSql = MysqlKeywords.SELECT.build(dynamicMppaer.master().value(), Collections.singletonList(new TableFieldValue(tableField, null)));
        //获取连接SQL
        String joinSql = DynamicMppaer.SqlBuild.buildSql(dynamicMppaer);
        //获取Where条件
        SqlSource sqlSource = new SqlSource(wrapper);
        //拼接SQL进行查询结果 , 该结果是主表的唯一字段， 作为删除条件
        return jdbcTemplate.queryForList(selectSql + joinSql + sqlSource.getSql(), sqlSource.getParams())
                .stream()
                .map(item -> item.get(tableField.getPropertyView()))
                .collect(Collectors.toList());
    }

    @Override
    @Transactional(rollbackFor = Throwable.class)
    public int insert(Map<String, Object> entity) {
        //校验新增参数是否有表连接关键字
        if (dynamicMppaer.slave().length > 0) {
            TableField tableField = dynamicTableExtractor.getTableFieldByColumn(dynamicMppaer.master().value(), dynamicMppaer.master().onField());
            Object onFieldValue = entity.get(tableField.getPropertyView());
            if (onFieldValue == null) {
                throw new DynamicMapperException(String.format("当有从表时,新增需要指定主表的 [%s] 字段的值",dynamicMppaer.master().onField()));
            }
            //对从表的连接关键字进行赋值
            for (DynamicTable dynamicTable : dynamicMppaer.slave()) {
                TableField slave = dynamicTableExtractor.getTableFieldByColumn(dynamicTable.value(), dynamicTable.onField());
                //没有这个值则新增
                entity.putIfAbsent(slave.getPropertyView(), onFieldValue);
            }
        }
        //对入参Map进行按表名分组
        Map<String, List<TableFieldValue>> group = this.parseFieldValue(entity);
        int count = 0;
        //遍历分组进行新增数据
        for (Map.Entry<String, List<TableFieldValue>> entry : group.entrySet()) {
            //获取新增语句SQL
            String sql = MysqlKeywords.INSERT.build(StringUtils.camelToUnderline(entry.getKey()), entry.getValue());
            logger.logPreparing(sql);
            //获取SQL参数
            Object[] params = entry.getValue().stream().map(TableFieldValue::getValue).toArray();
            logger.logParameters(params);
            //执行sql
            int c = jdbcTemplate.update(sql, params);
            count += c;
            logger.logTotal(c);
        }
        return count;
    }

    @Override
    @Transactional(rollbackFor = Throwable.class)
    public int delete(QueryWrapper<?> wrapper) {
        //代理Wrapper对象
        wrapper = new ProxyWrapper().apply(wrapper);
        //获取主从表之间相互关联的值在传入Wrapper条件匹配后的值集合
        List<Object> matchs = getUniqueCondition(wrapper);
        if (matchs.isEmpty()) {
            return 0;
        }
        //计数器
        AtomicInteger count = new AtomicInteger();
        //删除多表
        getDynamicTable().forEach(item -> {
            //执行删除
            String deleteSql = MysqlKeywords.DELETE.build(item.value());
            //构建删除条件
            SqlSource deleteSqlSource = new SqlSource(new QueryWrapper<>().in(item.onField(), matchs));
            //构建SQL
            String sql = deleteSql + deleteSqlSource.getSql();
            //输出日志
            logger.logPreparing(sql);
            //输出参数
            logger.logParameters(deleteSqlSource.getParams());
            //执行删除
            int c = jdbcTemplate.update(sql, deleteSqlSource.getParams());
            //输出执行结果
            logger.logTotal(c);
            count.addAndGet(c);
        });
        return count.get();
    }

    @Override
    @Transactional(rollbackFor = Throwable.class)
    public int update(Map<String, Object> entity, QueryWrapper<?> updateWrapper) {
        //代理Wrapper对象
        updateWrapper = new ProxyWrapper().apply(updateWrapper);
        //获取主从表之间相互关联的值在传入Wrapper条件匹配后的值集合
        List<Object> matchs = getUniqueCondition(updateWrapper);
        if (matchs.isEmpty()) {
            return 0;
        }
        //计数器
        AtomicInteger count = new AtomicInteger();
        //对入参Map进行按表名分组 并 遍历需要修改的表,对表进行修改
        this.parseFieldValue(entity).forEach((key, value) -> {
            //修改sql前缀
            String updateSql = MysqlKeywords.UPDATE.build(key, value);
            //构建修改条件
            DynamicTable dynamicTable = getDynamicTable().stream().filter(item -> item.value().equals(key)).findFirst().orElseThrow(() -> new DynamicMapperException(String.format("表: %s ,未定义@DynamicTable注解")));
            SqlSource updateSqlSource = new SqlSource(new QueryWrapper<>().in(dynamicTable.onField(), matchs));
            //获取占位符参数
            List<Object> params = value.stream().map(TableFieldValue::getValue).collect(Collectors.toList());
            params.addAll(Arrays.stream(updateSqlSource.getParams()).collect(Collectors.toList()));
            //获取执行SQL
            String sql = updateSql + updateSqlSource.getSql();
            //输出日志
            logger.logPreparing(sql);
            logger.logParameters(params.toArray());
            //执行修改
            int c = jdbcTemplate.update(sql, params.toArray());
            logger.logTotal(c);
            count.addAndGet(c);
        });
        return count.get();
    }

    @Override
    public Map<String, Object> selectOne(QueryWrapper<?> queryWrapper, boolean throwEx) {
        //代理Wrapper对象
        queryWrapper = new ProxyWrapper().apply(queryWrapper);
        List<Map<String, Object>> result = selectList(queryWrapper);
        if (result.size() > 1 && throwEx) {
            throw new DynamicMapperException("selectOne() 查询出的结果数量大于1");
        }
        if (result.isEmpty()) {
            return null;
        }
        return result.get(0);
    }

    @Override
    public long selectCount(QueryWrapper<?> queryWrapper) {
        //代理Wrapper对象
        queryWrapper = new ProxyWrapper().apply(queryWrapper);
        //获取Select语句
        String selectSql = MysqlKeywords.SELECT.getKeywords() + " COUNT(1) FROM " + dynamicMppaer.master().value() + " ";
        //获取连接SQL
        String joinSql = DynamicMppaer.SqlBuild.buildSql(dynamicMppaer);
        //获取where条件语句
        SqlSource sqlSource = new SqlSource(queryWrapper);
        //获取SQL
        String sql = selectSql + joinSql + sqlSource.getSql();
        //输出日志
        logger.logPreparing(sql);
        logger.logParameters(sqlSource.getParams());
        //查询结果
        Long count = jdbcTemplate.queryForObject(sql, sqlSource.getParams(), Long.class);
        count = count == null ? 0 : count;
        //输出日志
        logger.logColumns(new String[]{"COUNT(1)"});
        logger.logRow(new Object[]{count});
        logger.logTotal(1);
        return count;
    }

    @Override
    public List<Map<String, Object>> selectList(QueryWrapper<?> queryWrapper) {
        //代理Wrapper对象
        queryWrapper = new ProxyWrapper().apply(queryWrapper);
        //获取Select语句
        String selectSql = MysqlKeywords.SELECT.build(dynamicMppaer.master().value(),
                getDynamicTable()
                .stream()
                .collect(Collectors.toMap(DynamicTable::value,
                        value -> dynamicTableExtractor.getTableField(value.value())
                                .stream()
                                .map(item -> new TableFieldValue(item, null))
                                .collect(Collectors.toList()))));
        //获取连接SQL
        String joinSql = DynamicMppaer.SqlBuild.buildSql(dynamicMppaer);
        //获取where条件语句
        SqlSource sqlSource = new SqlSource(queryWrapper);
        //获取SQL语句
        String sql = selectSql + joinSql + sqlSource.getSql();
        //输出日志
        logger.logPreparing(sql);
        logger.logParameters(sqlSource.getParams());
        List<Map<String, Object>> result = jdbcTemplate.queryForList(sql, sqlSource.getParams());
        //输出结果日志
        logger.logColumns(result.stream().map(Map::keySet).findFirst().orElse(new HashSet<>()).toArray(new String[0]));
        result.stream().map(item -> item.values().toArray()).forEach(logger::logRow);
        logger.logTotal(result.size());
        return result;
    }

    @Override
    public Page<Map<String, Object>> selectPage(Page<?> page, QueryWrapper<?> queryWrapper) {
        //代理Wrapper对象
        queryWrapper = new ProxyWrapper().apply(queryWrapper);
        //获取Select语句
        String selectSql = MysqlKeywords.SELECT.build(dynamicMppaer.master().value(),
                getDynamicTable()
                        .stream()
                        .collect(Collectors.toMap(DynamicTable::value,
                                value -> dynamicTableExtractor.getTableField(value.value())
                                        .stream()
                                        .map(item -> new TableFieldValue(item, null))
                                        .collect(Collectors.toList()))));
        //获取连接SQL
        String joinSql = DynamicMppaer.SqlBuild.buildSql(dynamicMppaer);
        //获取where条件语句
        SqlSource sqlSource = new SqlSource(queryWrapper);
        //获取分页语句
        Return<String, Object[]> limitSql = getLimitSql(page);
        List<Object> params = Arrays.stream(sqlSource.getParams()).collect(Collectors.toList());
        params.addAll(Arrays.stream(limitSql.getR2()).collect(Collectors.toList()));
        //获取SQL语句
        String sql = selectSql + joinSql + sqlSource.getSql() + limitSql.getR1();
        //输出日志
        logger.logPreparing(sql);
        logger.logParameters(params.toArray());
        //查询分页
        List<Map<String, Object>> result = jdbcTemplate.queryForList(sql, params.toArray());
        //输出结果日志
        logger.logColumns(result.stream().map(Map::keySet).findFirst().orElse(new HashSet<>()).toArray(new String[0]));
        result.stream().map(item -> item.values().toArray()).forEach(logger::logRow);
        logger.logTotal(result.size());
        //构建分页结果数据
        Page<Map<String, Object>> resultPage = new Page<>();
        resultPage.setSize(page.getSize());
        resultPage.setCurrent(page.getCurrent());
        resultPage.setTotal(selectCount(queryWrapper));
        resultPage.setPages(page.getTotal() % page.getSize() == 0 ? page.getTotal() / page.getSize() : page.getTotal() / page.getSize() + 1);
        resultPage.setRecords(result);
        return resultPage;
    }

    /**
     * 获取分页数据
     * @param page
     * @return
     */
    private Return<String,Object[]> getLimitSql(Page<?> page){
        if (page == null) {
            return null;
        }
        return new Return<String, Object[]>() {
            @Override
            public String getR1() {
                return " LIMIT ? , ? ";
            }

            @Override
            public Object[] getR2() {
                return new Object[]{(page.getCurrent() - 1) * page.getSize() , page.getSize()};
            }
        };
    }

    @Override
    public void reload() {
        dynamicTableExtractor.reload();
    }
}
